package cn.cew.bytespyserver.tool;

import cn.cew.bytespyserver.comm.Constants;
import cn.cew.bytespyserver.controller.ClassInfo;
import cn.cew.bytespyserver.controller.MethodInfo;
import cn.cew.bytespyserver.jad.Jad;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaType;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * @author chenenwei
 * @version 1.0.0
 * @description TODO
 * @createTime 2022-10-13 17:04:00
 */
public class JarUtils {

    /**
     * 获取jar包中全部class
     * @param jarFile jar 绝对路径
     * @param packagePrefix 包名
     * @return
     */
    public static List<ClassInfo> listClass (String jarFile, String packagePrefix) throws IOException {

        List<ClassInfo> list = new ArrayList<>();
        JarFile jar = new JarFile(jarFile);
        Enumeration<JarEntry> enumFiles = jar.entries();
        JarEntry entry;
        while (enumFiles.hasMoreElements()) {
            entry = enumFiles.nextElement();
            if (!entry.getName().contains("META-INF")) {
                String classFullName = entry.getName();
                if (classFullName.contains(".class")) {
                    classFullName =  classFullName.substring(0, classFullName.length() - 6).replace("/", ".");
                    if (BaseUtils.isNotEmpty(packagePrefix)) {
                        if (classFullName.startsWith(packagePrefix)) {
                            String packageName = classFullName.substring(0, classFullName.lastIndexOf("."));
                            String className = classFullName.substring(classFullName.lastIndexOf(".") + 1);
                            list.add(new ClassInfo(packageName, className));
                        }
                    } else {
                        int end = classFullName.length();
                        int index = classFullName.lastIndexOf(".");
                        if (index == -1) {
                            continue;
                        }
                        String packageName = classFullName.substring(0, index);
                        String className = classFullName.substring(classFullName.lastIndexOf(".") + 1);
                        list.add(new ClassInfo(packageName, className));
                    }
                }
            }
        }
        return list;
    }

    /**
     * 获取类中的方法
     * @param jarFile jar包绝对路径
     * @param packageName 包名 cn.cew.xxx
     * @param className 类名 XXX
     * @return 方法列表
     */
    public static List<MethodInfo> listMethod (String jarFile, String packageName, String className) throws Exception {

        String name = packageName + "." + className;
        makTempClass(jarFile, name);
        Jad.decompile(name, null, name);
        List<MethodInfo> methodInfos = new ArrayList<>(2);
//        File f = new File(jarFile);
//        URL url = f.toURI().toURL();
//        URLClassLoader classLoader = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
//        Class<?> clz = classLoader.loadClass(packageName + "." + className);
        List<CtMethod> methodList = new ArrayList<>();
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(jarFile);
        CtClass ctClass = classPool.getCtClass(packageName + "." + className);
        methodList.addAll(Arrays.asList(ctClass.getMethods()));
        methodList.addAll(Arrays.asList(ctClass.getDeclaredMethods()));
        for (CtMethod method : methodList) {
            String genericSignature = method.getGenericSignature();
            System.out.println(genericSignature);
            String descriptor = method.getMethodInfo().getDescriptor();
            StringBuilder params  = new StringBuilder();
            CtClass[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                params.append(parameterTypes[i].getSimpleName());
                if (i + 1 == parameterTypes.length) {
                    break;
                }
                params.append(",");
            }
            String param = params.toString();
            String display = method.getName() + BaseUtils.append(param) + ": " + method.getReturnType().getSimpleName();

            MethodInfo methodInfo = MethodInfo.builder()
                    .packageName(packageName)
                    .className(className)
                    .name(method.getName())
                    .display(display)
                    .params(param)
                    .signature(descriptor)
                    .build();
            methodInfos.add(methodInfo);
        }
        return methodInfos;
    }


    /**
     * 创建临时class文件
     * @param jarPath jar包绝对路径
     * @param classFullName 全限定类名
     * @throws IOException
     */
    public static void makTempClass (String jarPath, String classFullName) throws IOException {

        URL url = new URL("jar:file:/" + jarPath + "!/" + classFullName.replace(".", "/") + ".class");
        System.out.println(url.getPath());
        InputStream in = url.openStream();
        byte[] bytes = new byte[1024];
        String tempFile = BaseUtils.getProjectPath() + File.separator + Constants.TEMP_CLASS_DIR + File.separator + classFullName + ".class";
        FileOutputStream out = new FileOutputStream(new File(tempFile));
        while (in.read(bytes) != -1) {
            out.write(bytes);
        }
    }

    /**
     * 获取方法体
     * @param javaFilePath java文件绝对路径,如 c:\\Test.java
     * @param methodName 方法名称
     * @param paramTypes 方法参数类型，如：String,int
     * @return 方法体
     * @throws IOException
     */
    public static String getMethodCodeBlock (String javaFilePath, String methodName, List<String> paramTypes) throws IOException {

        JavaProjectBuilder builder =  new  JavaProjectBuilder();
        //源码目录
        builder.addSource(new File(javaFilePath));
        Collection<JavaClass> classes = builder.getClasses();
        System.out.println(classes);
        for (JavaClass clz : classes) {
            List<JavaMethod> methods = clz.getMethods();
            for(JavaMethod m : methods){
                if (m.getName().equals(methodName)) {
                    List<JavaType> parameterTypes = m.getParameterTypes();
                    if (BaseUtils.isNotEmpty(parameterTypes) && BaseUtils.isNotEmpty(paramTypes)) {
                        List<String> params = parameterTypes.stream().map(JavaType::getValue).sorted().collect(Collectors.toList());
                        Collections.sort(paramTypes);
                        if (String.join("", params).equals(String.join("", paramTypes))) {
                            //方法体的源码
                            return m.getCodeBlock();
                        }
                    } else if (BaseUtils.isEmpty(parameterTypes) && BaseUtils.isEmpty(paramTypes)) {
                        List<String> params = parameterTypes.stream().map(JavaType::getValue).sorted().collect(Collectors.toList());
                        Collections.sort(paramTypes);
                        if (String.join("", params).equals(String.join("", paramTypes))) {
                            //方法体的源码
                            return m.getCodeBlock();
                        }
                    }
                }
            }
        }
        return "";
    }

    /**
     * 获取类代码块
     * @param packageName 包名
     * @param className 类名
     * @return 代码块
     * @throws IOException
     */
    public static String getClassCodeBlock (String packageName, String className) throws IOException {

        String javaPath =  BaseUtils.getProjectPath() + File.separator + Constants.TEMP_JAVA_DIR + File.separator + packageName + "." + className + ".java";
        BufferedReader reader = new BufferedReader(new FileReader(new File(javaPath)));

        StringBuilder builder = new StringBuilder();
        String line = "";
        while ((line = reader.readLine()) != null) {
            builder.append(line).append("\n\r");
        }
        return builder.toString();
    }
}
